#include "main.h"
#include <prlock.h>


threadpool_t *pool = NULL;

js_thread_t void_slot = { state: -1 };

JSBool branch_callback(JSContext *cx) {
	trace1("branch_callback");
	js_thread_t *x = JS_GetContextPrivate(cx);
	
	trace2("thread id=%ld at slot #%d: operation counter = %d", x->id, x->slot, x->branch_callback_counter);
	
	if (x->branch_callback_counter++ > 1000) {
		trace2("thread id=%ld at slot #%d: branch callback counter reset", x->id, x->slot);
		x->branch_callback_counter = 0;
	}
}



void init_threadpool() {
	trace1("init_threadpool");
	trace5("call");	
	pool = (threadpool_t *)calloc(sizeof(threadpool_t), 1);
	pool->lock = PR_NewLock();
	PR_Lock(pool->lock);
		pool->slots = (js_thread_t **)calloc(MAX_THREADS * (sizeof (js_thread_t *)), 1);
		pool->nslots = 0;
		pool->last_id = 10000000;
	PR_Unlock(pool->lock);
}


void js_thread_set_lock(js_thread_t *x, int bLock) {
	if (bLock)
		PR_Lock(x->lock);
	else
		PR_Unlock(x->lock);
}



/**
 *
 */
js_thread_t *get_thread_slot(JSContext *cx) {
	trace1("get_thread_slot()");

	unsigned long id;
	int i, slot;
	
	trace9("call");
	
PR_Lock(pool->lock);
	js_thread_t *x = NULL;
	js_thread_t *t = NULL;
	
	trace8("trying to find closed slot. there are %d slots", pool->nslots);
	
	for (i = pool->nslots - 1; i >= 0; --i) {
		t = pool->slots[i];
		if (t && t->closed) {
			x = t;
			x->closed = 0;
			slot = x->slot;
			id = x->id;
			trace8("reusing slot #%d (was id=%ld)", slot, id);
			break;
		}
	}
PR_Unlock(pool->lock);

	if (!x && (pool->nslots >= MAX_THREADS)) {
		trace2("limit of MAX_THREADS exceeded! using void slot");
		return &void_slot;
	}

PR_Lock(pool->lock);	
	if (!x) {
		trace5("creating new slot #%d", pool->nslots);
		x = (js_thread_t *)calloc(sizeof(js_thread_t), 1);
		if (!x) fatal("get_thread: calloc() returned NULL");
		x->slot = pool->nslots;
		x->lock = PR_NewLock();
		x->argv = (jsval *)malloc(10 * sizeof(jsval));

		//JS_AddValueRoot(

		pool->slots[pool->nslots] = x;
		++pool->nslots;
		trace5("new slot #%d created", pool->nslots);
	}
	id = pool->last_id++;
	trace9("initializing slot");
	
	js_thread_set_lock(x, 1);
	//PR_Lock(x->lock);		
		x->id = id;
		x->delay = 0;
		x->func = JSVAL_VOID;
		x->argc = 0;
		x->argv[0] = JSVAL_VOID;
		x->argv[1] = JSVAL_VOID;
		x->argv[2] = JSVAL_VOID;
		x->argv[3] = JSVAL_VOID;
		x->argv[4] = JSVAL_VOID;
		x->argv[5] = JSVAL_VOID;
		x->argv[6] = JSVAL_VOID;
		x->argv[7] = JSVAL_VOID;
		x->argv[8] = JSVAL_VOID;
		x->argv[9] = JSVAL_VOID;
		x->global = NULL;
	//PR_Unlock(x->lock);
	js_thread_set_lock(x, 0);
	
PR_Unlock(pool->lock);

	trace5("end");
	return x;	
}


void init_thread_context(js_thread_t *x) {
	trace1("init_thread_context");
	
	if (!x->cx) {
		trace5("creating new context");
		x->cx = create_context(NULL);
	} else
		JS_SetContextThread(x->cx);
	JS_SetContextPrivate(x->cx, x);


	if (!x->global)
		x->global = JS_GetGlobalObject(x->cx);
	else
		JS_SetGlobalObject(x->cx, x->global);
		
}


/**
 *
 */
void js_thread_proc(void *param) {
	trace1("js_thread_proc");
	
	js_thread_t *x = (js_thread_t *)param;

	if (x->state < 0) {
		trace2("Ignoring void slot");
		return;
	}
	
	//PR_Lock(x->lock);
	js_thread_set_lock(x, 1);
	
	int id = x->id, slot = x->slot;
	


	trace5("thread id=%ld at slot #%d: started *****************", id, slot);

	if (!JSVAL_IS_OBJECT(x->func))
		fatal("js_thread_proc: x->func is null");

	//jsval argv[1];
	//argv[0] = x->arg;
	jsval *argv = x->argv;
	
	jsrefcount rc;

	x->branch_callback_counter = 0;

	init_thread_context(x);

/*
	if (!x->cx) {
		trace5("thread id=%ld at slot #%d: creating new context", id, slot);
		x->cx = JS_NewContext(rt, 8096);
		JS_SetOptions(x->cx, JSOPTION_DONT_REPORT_UNCAUGHT | JSOPTION_VAROBJFIX | JSOPTION_JIT);
		JS_SetScriptStackQuota(x->cx, 16*1024*1024);
		//JS_SetOperationCallback(x->cx, branch_callback);
	} else {
		trace9("thread id=%ld at slot #%d: reusing old context", id, slot);
		JS_SetContextThread(x->cx);
	}
	JS_SetContextPrivate(x->cx, x);
*/	
	
	JS_BeginRequest(x->cx);
		JS_AddNamedValueRoot(x->cx, &x->func, "setTimeout callback");
		JS_AddNamedValueRoot(x->cx, x->argv+0,  "setTimeout callback argument 0");
	JS_EndRequest(x->cx);

	
	


	if (x->delay) {
		trace9("thread id=%ld at slot #%d: delay: waiting %d ms...", id, slot, x->delay);
		jsrefcount rc = JS_SuspendRequest(x->cx);
			PRStatus status = PR_Sleep(PR_MillisecondsToInterval(x->delay));
		JS_ResumeRequest(x->cx, rc);
		if (PR_SUCCESS != status) {
			trace2("clearTimeout");
			goto end;
		}
	}
	
	trace9("thread id=%ld at slot #%d: call js function", id, slot);
	JS_BeginRequest(x->cx);
		//JSBool ok = JS_CallFunctionValue(x->cx, NULL, x->func, x->arg ? 1 : 0, &x->arg, &x->rval);
		JSBool ok = JS_CallFunctionValue(x->cx, NULL, x->func, x->argc, x->argv, &x->rval);
	JS_EndRequest(x->cx);

	if (!ok){
		trace2("thread id=%ld at slot #%d: function failed!", id, slot);
		
		JS_BeginRequest(x->cx);
		if (JS_IsExceptionPending(x->cx)) {	
			jsval e;			
			JS_GetPendingException(x->cx, &e);
			fprintf(stdout, "%s:%s: %s\n",
				get_prop(x->cx, e, "fileName"), 
				get_prop(x->cx, e, "lineNumber"),
				//get_prop(x->cx, e, "message")
				vtoa(x->cx, e)
			);
		}
		JS_ClearPendingException(x->cx);
		JS_EndRequest(x->cx);
	}

end:

	JS_BeginRequest(x->cx);
		JS_RemoveValueRoot(x->cx, &x->func);
		JS_RemoveValueRoot(x->cx, x->argv+0);
	JS_EndRequest(x->cx);

	
	if(x->client) {
		trace2("Closing client socket");
		PR_Close(x->client);
		x->client = NULL;
	}

	if(x->join){
		trace5("joining thread");
		PR_JoinThread(x->join);
		x->join = NULL;
	}

	//JS_SetContextPrivate(x->cx, NULL);
	
	trace5("clearing js context");

	//JS_GC(x->cx);
	JS_MaybeGC(x->cx);
	JS_ClearContextThread(x->cx);


	
	x->closed = 1;

	js_thread_set_lock(x, 0);
	//PR_Unlock(x->lock);	

	trace5("thread id=%ld at slot #%d: finished", id, slot);
}


